﻿// The MIT License (MIT)
// Copyright (c) 2013 Stylianos Dritsas
//
// Permission is hereby granted, free of charge, to any person obtaining a 
// copy of this software and associated documentation files (the "Software"), 
// to deal in the Software without restriction, including without limitation 
// the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the 
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included 
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
// DEALINGS IN THE SOFTWARE.

//-- System Imports
//--
using System;
using System.Windows.Forms;
using System.Threading;
using System.Collections.Generic;

//-- Library Imports
//--
using Rhino.Geometry;
using Rhino.Display;

namespace Sutd
{
  public partial class Command
  {
    public class Evolution
    {
      private static Thread       thread;     //-- Animation Thread
      private static Conduit      conduit;    //-- Display Conduit
      private static Dialog       dialog;     //-- Control Panel
      private static Surface      surface;    //-- Search Surface
      private static System<Data> system;     //-- Evolution System

      #region -- Evolution System ---------------------------------------------

      public class System<DataType>
      {
        #region -- Callbacks ----------------------------------------------------

        public delegate void    MethodCreate   ( ref DataType self );
        public delegate double  MethodEvaluate ( ref DataType self );
        public delegate double  MethodReset    ( ref DataType self, Random random );
        public delegate void    MethodMutate   ( ref DataType self, Random random );
        public delegate void    MethodCombine  ( ref DataType self, ref DataType next, ref DataType result );
        public delegate bool    MethodVisit    ( ref DataType self, double fitness, int index );
        public delegate bool    MethodReport   ( System<DataType> system );
        #endregion

        #region -- Candidate Wrapper ------------------------------------------

        protected class Candidate : IComparable<Candidate>
        {
          public static int Direction = 1;

          public DataType data;
          public double   fitness;

          public int CompareTo( Candidate that )
          {
            return ( this.fitness <= that.fitness ) ? Direction : -Direction;
          }
        }

        #endregion

        #region -- Members ------------------------------------------------------

        protected Candidate[]    candidates;

        protected MethodCreate   create;
        protected MethodReset    reset;
        protected MethodCombine  combine;
        protected MethodEvaluate evaluate;
        protected MethodMutate   mutate;
        protected MethodReport   report;

        protected Random         random;

        protected uint           mutation;
        protected uint           preserve;
        protected uint           generation;

        #endregion

        #region -- Creation -----------------------------------------------------

        public System(
          MethodCreate   create,
          MethodReset    reset,
          MethodEvaluate evaluate,
          MethodCombine  combine,
          MethodMutate   mutate,
          MethodReport   report,

          uint population = 100,
          uint mutation   = 5,
          uint preserve   = 10,
          uint randomize  = 0 )
        {
          this.create   = create;
          this.reset    = reset;
          this.combine  = combine;
          this.evaluate = evaluate;
          this.mutate   = mutate;
          this.report   = report;

          this.candidates = new Candidate[population];
          for( var index = 0; index < candidates.Length; index++ )
          {
            candidates[index] = new Candidate( );
            create( ref candidates[index].data );
          }

          this.mutation = mutation;
          this.preserve = preserve;

          this.random = new Random( (int)( ( randomize == 0 ) ? DateTime.Now.ToFileTime( ) : randomize ) );
        }

        #endregion

        #region -- Evolution ----------------------------------------------------

        public void Reset( bool asceding = true )
        {
          Candidate.Direction = asceding ? 1 : -1;

          foreach( var candidate in candidates )
          {
            candidate.fitness = reset( ref candidate.data, random );
          }

          generation = 0;
        }

        public void Score( )
        {
          foreach( var candidate in candidates )
          {
            candidate.fitness = evaluate( ref candidate.data );
          }
        }

        public void Cross( )
        {
          for( var index = preserve; index < candidates.Length; index++ )
          {
            var aitem = random.Next( ) % preserve;
            var bitem = random.Next( ) % candidates.Length;

            combine(
              ref candidates[aitem].data,
              ref candidates[bitem].data,
              ref candidates[index].data
            );
          }
        }

        public void Mutate( )
        {
          for( var index = preserve; index < candidates.Length; index++ )
          {
            if( random.Next( 100 ) < mutation )
            {
              mutate( ref candidates[index].data, random );
            }
          }
        }

        public void Evolve( int iterations = 1 )
        {
          for( var iteration = 0; iteration < iterations; iteration++ )
          {
            Cross( );
            Mutate( );
            Score( );
            Array.Sort( candidates );
            if( !report( this ) ) break;
            ++generation;
          }
        }

        #endregion

        #region -- Access -------------------------------------------------------

        public DataType this[int index]
        {
          get
          {
            return candidates[index].data;
          }
        }

        public double Fitness( int index )
        {
          return candidates[index].fitness;
        }

        public int Population
        {
          get
          {
            return candidates.Length;
          }
        }

        public uint Generation
        {
          get
          {
            return generation;
          }
        }

        public void ForEach( MethodVisit visitor )
        {
          for( var index = 0; index < candidates.Length; index++ )
          {
            var candidate = candidates[index];
            if( !visitor( ref candidate.data, candidate.fitness, index ) ) break;
          }
        }

        #endregion
      }

      #endregion

      #region -- Candidate Solution -------------------------------------------

      protected struct Data
      {
        //-- Reparameterization
        //--
        public static double uo; 
        public static double du; 

        public static double vo; 
        public static double dv; 

        //-- Point on Surface Data
        //--
        public double  u;        
        public double  v;

        public Point3d point;    
      }

      #endregion

      #region -- Display Conduit ----------------------------------------------

      //-- Dynamic Viewport Drawing 
      //--
      public class Conduit : Rhino.Display.DisplayConduit
      {
        #region -- Bounding Box -----------------------------------------------

        //-- Respond to View Changes
        //--
        protected override void CalculateBoundingBox( CalculateBoundingBoxEventArgs args )
        {
          args.IncludeBoundingBox( surface.GetBoundingBox( true ) );
        }

        //-- Respond to Zoom Changes
        //--
        protected override void CalculateBoundingBoxZoomExtents( CalculateBoundingBoxEventArgs args )
        {
          args.IncludeBoundingBox( surface.GetBoundingBox( true ) );
        }

        #endregion

        #region -- Dynamic Display --------------------------------------------

        protected override void PostDrawObjects( DrawEventArgs args )
        {
          system.ForEach( ( ref Data data, double fitness, int index ) => 
          {
            args.Display.DrawPoint( data.point, System.Drawing.Color.Red );
            return true;
          } );
        }

        #endregion
      }

      #endregion

      #region -- Threading ----------------------------------------------------

      public static void Update( )
      {
        try
        {
          system.Evolve( 1 );
          Rhino.RhinoDoc.ActiveDoc.Views.Redraw( );
        }
        catch { }
      }

      public static ManualResetEvent pause = new ManualResetEvent( true  );
      public static ManualResetEvent abort = new ManualResetEvent( false );
      public static bool running = false;

      public static void StartStop( )
      {
        if( thread == null )
        {
          thread = new Thread( ( ) =>
          {
            while( true )
            {
              pause.WaitOne( Timeout.Infinite );
              if( abort.WaitOne( 0 ) ) break;
              Update( );
              Thread.Sleep( 200 );
            }
          } );
          thread.Start( );
        }

        if( running )
        {
          pause.Reset( );
        }
        else
        {
          pause.Set( );
        }

        running = !running;
      }

      #endregion

      #region -- Dialog -------------------------------------------------------

      public class Dialog : Form
      {
        public Dialog( ) : base( )
        {
          Text = "Evolution";
          Width  = 200;
          Height = 100;

          SuspendLayout( );

          var active = new Button( );
          active.Text = "Stopped";
          active.Click += ( sender, args ) =>
          {
            StartStop( );

            if( running )
            {
              active.Text = "Running";
            }
            else
            {
              active.Text = "Stopped";
            }
          };
          active.Dock = DockStyle.Fill;

          Controls.Add( active );

          ResumeLayout( );
        }
      }

      #endregion

      #region -- Life Cycle ---------------------------------------------------

      //-- Search for Global Minimum
      //-- Point on Surface
      //--
      public static void Demo( )
      {
        //-- Halt Animation
        //--
        if( running )
        {
          pause.Reset( );
          running = false;
        }

        //-- Request User Input
        //--
        Rhino.DocObjects.ObjRef obj;
        if( Rhino.Input.RhinoGet.GetOneObject( "Select Search Surface", false, Rhino.DocObjects.ObjectType.Surface, out obj ) != Rhino.Commands.Result.Success ) return;
        surface = obj.Surface( );
        
        var ud = surface.Domain( 0 );
        var vd = surface.Domain( 1 );

        Data.uo = ud.Min; Data.du = ud.Max - ud.Min;
        Data.vo = vd.Min; Data.dv = vd.Max - vd.Min;

        //-- Number of Cadidates
        //--
        int population = 100;
        if( Rhino.Input.RhinoGet.GetInteger( "Population Size", true, ref population, 10, 1000 ) != Rhino.Commands.Result.Success ) return;

        //-- Number of Top Candidates Preserved
        //--
        int preserve = 5;
        if( Rhino.Input.RhinoGet.GetInteger( "Preserve Best Count", true, ref preserve, 1, population ) != Rhino.Commands.Result.Success ) return;

        //-- Percent of Mutation
        //--
        int mutation = 15;
        if( Rhino.Input.RhinoGet.GetInteger( "Mutation Rate", true, ref mutation, 0, 100 ) != Rhino.Commands.Result.Success ) return;

        //-- Percent Mutation Range
        //--
        int noise_level = 10;
        if( Rhino.Input.RhinoGet.GetInteger( "Noise Levels", true, ref noise_level, 0, 100 ) != Rhino.Commands.Result.Success ) return;
        var noise = 0.01 * noise_level;

        //-- Build Evolutionary System
        //--        
        system = new System<Data>(

          //-- Allocate Candidates
          //--
          ( ref Data data ) => { data = new Data( ); },

          //-- Initialize Candidate Pool
          //--
          ( ref Data data, Random random ) => 
          { 
            //-- Random Parameter on Surface
            //--
            data.u = random.NextDouble( );
            data.v = random.NextDouble( );

            //-- Evaluate for Visualization
            //--
            data.point = surface.PointAt( 
              Data.uo + data.u * Data.du ,
              Data.vo + data.v * Data.dv 
            );
            return data.point.Z;
          },

          //-- Evaluate Candidate
          //--
          ( ref Data data ) =>
          {
            //-- Trivial Evaluation
            //--
            data.point = surface.PointAt( 
              Data.uo + data.u * Data.du ,
              Data.vo + data.v * Data.dv 
            );

            return data.point.Z;
          },

          //-- Combine Candidates
          //--
          ( ref Data source, ref Data target, ref Data result ) =>
          {
            //-- Interpolation Only?
            //--
            result.u = 0.5 * ( source.u + target.u );
            result.v = 0.5 * ( source.v + target.v );
          },

          //-- Mutation
          //--
          ( ref Data data, Random random ) =>
          {
            //-- May Jump 10%
            //--
            data.u += noise * ( 1.0 - 2.0 * random.NextDouble( ) );
            data.v += noise * ( 1.0 - 2.0 * random.NextDouble( ) );

            //-- Stay with Unit Range
            //--
            if( data.u < 0 ) data.u = 0; if( data.u > 1 ) data.u = 1;
            if( data.v < 0 ) data.v = 0; if( data.v > 1 ) data.v = 1;
          },

          //-- Report 
          //--
          ( evo ) => 
          {
            //-- Plot Best Candidate's Fitness
            //--
            Rhino.RhinoDoc.ActiveDoc.Objects.AddPoint(
              new Point3d( evo.Generation, evo.Fitness( 0 ), 0 )
            );
            return true;
          },

          (uint)population, 
          (uint)mutation,   
          (uint)preserve 
        );

        //-- Reset System
        //--
        system.Reset( false );

        //-- UI Update
        //--
        if( dialog == null )
        {
          dialog = new Dialog( );
        }
        if( !dialog.Visible )
        {
          dialog.Show( Rhino.RhinoApp.MainWindow( ) );
        }

        //-- Rhino Display
        //--
        if( conduit == null )
        {
          conduit = new Conduit( );
          conduit.Enabled = true;
        }
      }

      #endregion
    }

    //-- Rhino Command Invoked by Name
    //--
    public static void evolve( )
    {
      try
      {
        Evolution.Demo( );
      }
      catch( Exception except )
      {
        Plot( except.ToString( ) );
      }
    }
  }
}